
class WebRtcClient {

    /**
     *
     * @param userId                                      // 用户
     * @param token                                       // Im Token
     * @param tenantCode                                  // 租户编码
     * @param signalWebSocketUrl                          // WebRtc信令地址
     * @param isAudio                                     // 是否语音通信 true: 是。 false：视频通信
     * @param localVideo                                  // 本地Video
     * @param remoteContainerId                           // 远程RemoteAudio容器ID
     */
    constructor(userId, token, tenantCode, signalWebSocketUrl, isAudio, localVideo, remoteContainerId) {
        this.userId = userId;
        this.token = token;
        this.tenantCode = tenantCode;
        this.websocketUrl = signalWebSocketUrl;
        this.isAudio = isAudio;
        this.localVideo = localVideo;
        this.remoteContainerId = remoteContainerId;

        this.localMedialStream = null;                     // 本地视频流
        this.remoteUserId = null,                          // 有值，就是p2p通信，否则多人通信
        this.remoteUserIds = [];                           // 多人通信

        this.iceUsername = null;
        this.icePassword = null;

        this.candidates = [];
        this.endOfcandidates = false;

        this.roomName = null;                                // 房间名称
        this.audioContext = null;

        console.info("userId and websocketUrl", userId, signalWebSocketUrl);
        this.signalWebSocket = SignalWebSocket.getInstance();
        this.signalWebSocket.initCallback(this.initRouter.bind(this));
        this.signalWebSocket.userId = userId;
        this.signalService = new SignalService();

        this.peerRtcConnections = [];                   // 多人远程连接(非p2p)
    }

    // 开启连接WebRtc信令服务器并登录以及本地视频流
    async start() {
        try{
            await this.openLocalVideo();
            await this.signalWebSocket.openWebsocket(this.websocketUrl);
            console.info("websocket isActive:", this.signalWebSocket.isActive, websocketIsActive);
            await this.signalService.login(this.userId, this.token, this.tenantCode);
        } catch (error) {
            console.error("connect websocket", error);
        }
    }

    /**
     * 创建房间
     * 用户发起视频（or语音）聊天时，需要创建房间
     * 房间创建成功后，进行IM语音邀请
     * @returns {Promise<void>}
     */
    async createRoom() {
        this.peerRtcConnection = new RTCPeerConnection();
        let chatType = this.isAudio ? ChatType.audio : ChatType.video;
        let resp = await this.signalService.createRoom(chatType);
        if (this.signalService.isError(resp)) {
            console.error('[WebRtcClient createRoom failure]', JSON.stringify(resp));
            this.close();
            return;
        }
        this.roomName = resp['otherInfo'] || resp.otherInfo;
        console.info('[WebRtcClient createRoom success]', this.roomName);
    }

    /**
     * 接受到IM 邀请视频（or语音）聊天时，进行入房操作
     * @param roomName
     * @returns {Promise<void>}
     */
    async joinRoom(roomName) {
        let resp = await this.signalService.joinRoom(roomName);
        if (this.signalService.isError(resp)) {
            console.error('[WebRtcClient joinRoom failure]', JSON.stringify(resp));
            this.close();
            return;
        }
        this.roomName = resp['otherInfo'] || resp.otherInfo;
        console.info('WebRtcClient joinRoom success]', this.roomName);
        return resp;
    }

    async exitRoom() {
        console.info("[WebRtcClient exitRoom]", this.isClosed);
        if (Objects.isNotNull(this.roomName) && this.isClosed === false) {
            this.isClosed = true;
            try {
                await this.signalService.exitRoom(this.roomName);
            } catch(error) {
                console.info("[WebRtcClient exitRoom, error]", error);
            }
        }
        this.close();
    }

    // 打开本地视频流（或音频流）
    async openLocalVideo() {
        console.info("openLocalVideo", this.isAudio);
        if (this.isAudio) {
            let localMediaStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
            this.localVideo.srcObject = localMediaStream;
            this.localMedialStream = localMediaStream;
            this.audioHandle(localMediaStream);
        } else {
            let localMediaStream = await navigator.mediaDevices.getUserMedia({ video: {
                    height: {min: 640, ideal: 1920},
                    width: {min: 360, ideal: 1080},
                    aspectRatio: 9/16
                }, audio: true });
            this.localVideo.srcObject = localMediaStream;
            this.localMedialStream = localMediaStream;
            this.audioHandle(localMediaStream);
        }
    }

    audioHandle(stream) {
        try {
            const audioContext = new AudioContext();
            const source = audioContext.createMediaStreamSource(stream);
            const destination = audioContext.createMediaStreamDestination();
            source.connect(destination);

            // 创建并配置音频处理器，启用回声消除和噪声抑制
            const audioProcessor = audioContext.createScriptProcessor(0, 1, 1);
            source.connect(audioProcessor);
            audioProcessor.connect(audioContext.destination);
            audioProcessor.onaudioprocess = function(e) {
                // 这里可以添加自定义的音频处理逻辑，例如回声消除或噪声抑制
                // 但通常我们依赖于浏览器的内置处理，所以这里可以留空或添加其他处理逻辑。
            };
        } catch (error) {
            console.error('')
        }
    }

    // 调整音量
    adjustVolume(volumeNumber) {
        if (this.audioContext === null) {
            this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
        }
        // 创建 MediaStreamAudioSourceNode
        const source = this.audioContext.createMediaStreamSource(stream);

        // 创建 GainNode 来控制音量
        const gainNode = this.audioContext.createGain();
        gainNode.gain.value = volumeNumber; // 设置放大倍数，这里设置为2倍

        // 连接音源和增益节点
        source.connect(gainNode);

        // 将增益节点连接到扬声器或进一步处理
        gainNode.connect(audioContext.destination);
    }

    // 是否开启麦克风
    setAudioStatus(isEnable) {
        if (this.localMedialStream !== null) {
            this.localMedialStream.getAudioTracks().forEach(track => {
                track.enabled = isEnable;
            });
        }
    }

    // 是否开启摄像头
    setVideoStatus(isEnable) {
        if (this.localMedialStream !== null) {
            this.localMedialStream.getVideoTracks().forEach(track => {
                track.enabled = isEnable;
            });
        }
    }

    // 切换镜头
    async switchCamera() {
        try {
            // 获取当前可用的媒体设备
            const devices = await navigator.mediaDevices.enumerateDevices();
            const videoDevices = devices.filter(device => device.kind === 'videoinput');

            // 切换摄像头设备
            const currentDevice = videoDevices.find(device => device.deviceId === this.localMedialStream.getVideoTracks()[0].getSettings().deviceId);
            const nextDevice = videoDevices.find((device, index) => device !== currentDevice && index < videoDevices.length);

            // 停止当前流
            this.localMedialStream.getTracks().forEach(track => track.stop());

            // 重新获取新的流
            const newStream = await navigator.mediaDevices.getUserMedia({
                video: {
                    deviceId: { exact: nextDevice.deviceId },
                    height: {min: 640, ideal: 1920},
                    width: {min: 360, ideal: 1080},
                    aspectRatio: 9/16 },
                audio: true
            });

            // 更新视频元素或处理新流
            if (this.localVideo != undefined) {
                this.localVideo.srcObject = newStream;
            }
            this.localMedialStream = newStream; // 更新当前流引用
            this.audioHandle(newStream);
        } catch (error) {
            console.error('Error accessing the camera', error);
        }
    }

    initRouter(callbackMap) {
        callbackMap.set("pingToClient", this.pingToClient.bind(this));

        callbackMap.set("serverSendRoomJoin", this.receiveJoinRoomReq.bind(this));
        callbackMap.set("serverSendRoomExit", this.receiveExitRoomReq.bind(this));
        callbackMap.set("serverSendSdp", this.receiveSdp.bind(this));
        callbackMap.set("serverSendCandidate", this.receiveIceCandidateReq.bind(this));
    }

    /**************************************websocket 接受消息*******************************************************************/
    async pingToClient(rpcData) {
        console.info('[WebRtcClient heart check]', 'pong');
        return new PongResp();
    }

    // 接受服务器转发用户入房间请求
    receiveJoinRoomReq(rpcData) {
        let joinRoomReq = rpcData.data;
        console.info('[WebRtcClient receive join room data]', joinRoomReq);
        setTimeout(async ()=> await this.createOffer(rpcData.data), 0);
        let resp = new OkResp();
        return resp;
    }

    /**
     * 接受服务端转发用户退出请求
     * @param exitRoomReq
     */
    receiveExitRoomReq(rpcData) {
        console.info("[WebRtcClient receive exit room data]", rpcData.data);
        let exitRoomReq = rpcData.data;
        let userId = exitRoomReq.userId;
        let peerRtcConnection = this.peerRtcConnections[userId];
        if (Objects.isNotNull(peerRtcConnection)) {
            let index = this.peerRtcConnections.indexOf(peerRtcConnection);
            this.peerRtcConnections.splice(index, 1);
            peerRtcConnection.close();

            let container = document.getElementById(this.remoteContainerId);
            let videoPane = document.getElementById(userId);
            container.removeChild(videoPane);
        }
        let resp = new OkResp();
        return resp;
    }

    /**
     * 接受服务端转发的Sdp请求
     * @param sdpReq
     * @returns {Promise<void>}
     */
    async receiveSdp(rpcData) {
        let sdpReq = rpcData.data;
        console.info("[WebRtcClient receive sdp data]", sdpReq, this.peerRtcConnections);
        let remoteUserId = sdpReq.userId;
        let sdpType = sdpReq.sdpType;
        if (sdpType === SdpType.offer) { // 连接端
            let offerSdp = new RTCSessionDescription({
                'type': 'offer',
                'sdp': sdpReq.sdp
            });
            let remotePeerConnection = this.createPeerRtcConnection(remoteUserId);
            await remotePeerConnection.setRemoteDescription(offerSdp);
            let answerSdp = await remotePeerConnection.createAnswer();
            await remotePeerConnection.setLocalDescription(answerSdp);
            await this.signalService.sendSdp(this.roomName, sdpReq.userId, answerSdp.sdp, SdpType.answer);
        } else if (sdpType === SdpType.answer) { // 发起端
            let localPeerConnection = this.peerRtcConnections[remoteUserId];
            if (Objects.isNotNull(localPeerConnection)) {
                let answerSdp = new RTCSessionDescription({
                    'type': 'answer',
                    'sdp': sdpReq.sdp
                });
                await localPeerConnection.setRemoteDescription(answerSdp);
            }
        } else {
            console.error('不支持这种SdpType:', sdpType);
        }
        let resp = new OkResp();
        return resp;
    }

    async receiveIceCandidateReq(rpcData) {
        let candidateReq = rpcData.data;
        console.info("[WebRtcClient receive iceCandidate data]", candidateReq);
        let remoteUserId = candidateReq.userId;
        let peerRtcConnection = this.peerRtcConnections[remoteUserId];
        if (Objects.isNotNull(peerRtcConnection)) {
            let candidate = new RTCIceCandidate({
                sdpMLineIndex: candidateReq.iceCandidate.sdpMLineIndex,
                candidate: candidateReq.iceCandidate.candidate
            });
            await peerRtcConnection.addIceCandidate(candidate);
        }
        let resp = new OkResp();
        return resp;
    }

    /*********************************************************************************************************/
    createPeerRtcConnection(remoteUserId) {
        let peerRtcConnection = this.peerRtcConnections[remoteUserId];
        if (Objects.isNull(peerRtcConnection)) {
            peerRtcConnection = this.newPeerRtcConnection();
            peerRtcConnection.onconnectionstatechange = this.connectionStateChange.bind(this);
            peerRtcConnection.onicecandidate = async (event)=>{ //this.handleLocalIceCandidate.bind(this);
                console.log('[WebRtcClient local Candidate change]', remoteUserId, event.candidate);
                if (Objects.isNotNull(event.candidate)) {
                    await this.signalService.iceCandidate(this.roomName, remoteUserId, event.candidate);
                } else {
                    console.log('[WebRtcClient End of candidates]');
                }
            };
            try {
                this.localMedialStream.getTracks().forEach(track=> {
                    console.info('[WebRtcClient open add task]', this.localMedialStream);
                    peerRtcConnection.addTask(track, this.localMedialStream)
                });
            } catch (e) {
                peerRtcConnection.addStream(this.localMedialStream);
            }
            peerRtcConnection.ontrack = (event)=>{
                console.info('[WebRtcClient receive track]', event.track.kind, event);
                if (event.track.kind === "video") {
                    let container = document.getElementById(this.remoteContainerId);
                    let video  = document.createElement('video');
                    video.srcObject   = event.streams[0];
                    video.autoplay    = true;
                    video.muted       = false;
                    video.id          = remoteUserId;
                    video.playsinline = true;
                    video.width = 200;
                    video.height = 200;
                    video.style.backgroundColor = '#333';
                    container.appendChild(video);
                    console.log('[WebRtcClient Append video element]', video);
                }
            };
            this.peerRtcConnections[remoteUserId] = peerRtcConnection;
        }
        return peerRtcConnection;
    }

    async createOffer(joinRoomReq) {
        let remoteUserId = joinRoomReq['userId'];
        let localPeerConnection = this.createPeerRtcConnection(remoteUserId);
        let offerSdp = await localPeerConnection.createOffer();
        await localPeerConnection.setLocalDescription(offerSdp);
        console.info('[WebRtcClient create offer]', offerSdp);
        await this.signalService.sendSdp(this.roomName, remoteUserId, offerSdp.sdp, SdpType.offer);
    }

    async connectionStateChange(event) {
        console.info('[WebRtcClient connection state change]', event);
    }

    async handleLocalIceCandidate(event) {
        console.log('[WebRtcClient local Candidate change]', event.candidate);
        if (Objects.isNotNull(event.candidate)) {
            await this.signalService.iceCandidate(this.roomName, event.candidate);
        } else {
            console.log('[WebRtcClient End of candidates]');
        }
    }

    async handleRemoteIceCandidate(event) {
        console.log('[WebRtcClient receive remote Candidate]', event.candidate);
        if (Objects.isNotNull(event.candidate)) {
            await this.signalService.iceCandidate(this.roomName, event.candidate);
        } else {
            console.log('[WebRtcClient receive remote candidates end]');
        }
    }

    onTrack(event) {

    }

    close() {
        console.info('[WebRtcClient close]');
        for (let remotePeerRtcConnection in this.peerRtcConnections.values()) {
            remotePeerRtcConnection.close();
        }
    }

    newPeerRtcConnection() {
        return new RTCPeerConnection();
    }
}